
if defined? Test::Unit::AssertionFailedError
  TQBaseException = Test::Unit::AssertionFailedError
# elsif defined? RSPec failed exc
  # TQBase...
else
  TQBaseException = StandardError
end

class PropertyBrokenException < TQBaseException
  attr :breaking_values
  def initialize(desc, num, *values)
    @breaking_values = values
    super(%{Property "#{desc}" broken in #{num} attempts with values: #{values.inspect}})
  end
end

module Test
  module Quick

    # A Char is a String of size 1
    # so we just create an object using the String constructore
    # and then we #freeze it
    class Char < String
      def initialize(obj)
        super(obj)
        #XXX refactor in proper exception
        raise "Must be a single Character" unless size == 1
        freeze
      end
    end

    class Property

      @@default_attempts_number = 500
      @@properties = Array.new

      attr_accessor :generators
      attr_accessor :description

      def initialize(description, *types, &check)
        @description = description

        @generators = types.map { |type| Mapping[type].new }

        @check = check

        @@properties.push self
      end

      def size
        @@properties.size
      end

      def self.all
        @@properties
      end

      def self.reset_properties
        @@properties = []
      end

      def self.default_attempts_number
        @@default_attempts_number
      end

      def self.default_attempts_number=(number)
        @@default_attempts_number = number
      end

      def all
        @@properties
      end

      def check(test_number=@@default_attempts_number)
        # we need to generate the values first
        test_number.times do |i|
          values = @generators.map {|g| g.next }
          @check.call(*values) or
          raise PropertyBrokenException.new(description, i, *values)
        end
        test_number
      end

    end

    module Generators

      class IntegerGen
        def initialize
          #XXX significant values would be nicer :)
          @min = -2**30
          @max =  2**32 -1
        end
        def next
          @min+rand(@max)
        end
      end

      class FloatGen
        def initialize
        end
        def next
          rand
        end
      end

      class CharGen
        CHARS= ('a'..'z').map {|c| Char.new(c)}
        def initialize
        end
        def next
          CHARS[rand(CHARS.size)]
        end
      end

      class StringGen
        CHARS= ('a'..'z').map {|c| Char.new(c)}
        def initialize
        end

        def next
          string_length = 1 + rand(50)
          string = String.new
          string_length.times do
            string += CHARS[rand(CHARS.size)]
          end
          string
        end

      end

      class Composed
        def initialize(*types)
          @generators = types.map { |type| type.new }
        end

        def next
          @generators.map { |g| g.next }
        end
      end
    end

    Mapping = {
      Integer => Generators::IntegerGen,
      Float   => Generators::FloatGen,
      Char    => Generators::CharGen,
      String  => Generators::StringGen
    }

    def compose(*types)
      Generators::Composed.new(types)
    end

    def two(type)
    end

    def three(type)
    end

    def vector(type, times)
    end

  end
end
